﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;

namespace GeneralClasses
{
    /// <summary>
    /// Shift class that canbe used both to hold an actual shift or used as a template for other shifts, by way of the Clone() method.
    /// </summary>
    public class Shift : ICloneable
    {
        #region variables

        private int shid; // shift ID from table, use to identify this specific instance of a shift. 
        private String name; // like "Morning Shift", etc
        private DateTime startTime; // usually used just for hours but can hold more for the templates
        private DateTime endTime;
        private DatabaseConnection dbc;
        private int departmentID; // the ID of the place that this shift takes place in.

        private int titleNeeded;
        private List<Person> persons; // the list of people assigned in this shift; these are local pointers to the master list

        private DayOfWeek dayOfWeek; // this Shift's dayOfWeek, based on it's weekly setting.

        private List<Skill> skills; // skills that this specific shift might need
        public Schedule schedulePointer; // pointer to the container class

        #endregion

        #region properties
        public int TitleNeeded {
            get { return titleNeeded; }
            set { titleNeeded = value; }
        }
        public String SkillString {
            get {
                String temp = "";
                foreach (Skill skill in skills) { 

                }
                return temp;
            }
        }
        public Schedule SchedulePointer
        {
            get { return schedulePointer; }
            set { schedulePointer = value; }
        }

        public List<Skill> Skills
        {
            get { return skills; }
            set { skills = value; }
        }

        public List<Person> Persons
        {
            get { return persons; }
            set { persons = value; }
        }


        public DayOfWeek DayOfWeek
        {
            get { return dayOfWeek; }
            set { dayOfWeek = value; }
        }

        public int getDepartmentID
        {
            get { return departmentID; }
            set { departmentID = value; }
        }

        public String Name
        {
            get { return name; }
            set { name = value; }
        }

        public int ShiftID
        {
            get { return shid; }
            set { shid = value; }
        }



        public DateTime StartTime
        {
            get
            {
                return startTime;
            }
            set
            {
                startTime = value;
            }
        }

        public DateTime EndTime
        {
            get
            {
                return endTime;
            }
            set
            {
                endTime = value;
            }
        }


        #endregion

        #region constructor
        //====================================================================
        /// <summary>
        /// empty constructor, should probably use the other one with the scope (in time)
        /// </summary>
        public Shift()
        {
            initialize();
        }

        /// <summary>
        /// constructor that sets the time that this shift runs in. the datetime class holds day and time, which both needs to be set
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        public Shift(DateTime start, DateTime end)
        {
            initialize();

            this.startTime = start;
            this.endTime = end;
        }

        /// <summary>
        /// for use with cloning, returns a new shift object with teh same data, but pointing to a new instance
        /// </summary>
        /// <param name="other"></param>
        private Shift(Shift other)
        {
            this.schedulePointer = other.schedulePointer;
            this.dayOfWeek = (DayOfWeek)other.dayOfWeek;
            this.dbc = (DatabaseConnection)other.dbc;
            this.departmentID = (int)other.departmentID;
            this.endTime = (DateTime)other.endTime;
            this.name = (String)other.name;
            this.shid = -1; // do not copy the other shift ID!
            this.startTime = (DateTime)other.startTime;
            this.titleNeeded = (int)other.titleNeeded;
            skills = new List<Skill>();
            persons = new List<Person>();


            foreach (Person p in other.persons)
            {
                this.persons.Add((Person)p);
            }
            foreach (Skill s in skills)
            {
                this.skills.Add((Skill)s);
            }
        }

        private void initialize()
        {
            this.titleNeeded = -1;
            skills = new List<Skill>();
            this.persons = new List<Person>();
            this.dbc = new DatabaseConnection();
            this.departmentID = -1;
            this.startTime = new DateTime();
            this.endTime = new DateTime();
            this.name = "Default Shift";
            this.shid = -1;
            this.dayOfWeek = new DayOfWeek();
        }

        #endregion

        #region private

        /// <summary>
        /// removes all relations that this shift might have
        /// used when removing the shift itself
        /// </summary>
        /// <returns>whether it was successful or not</returns>
        private bool removeRelations()
        {
            try
            {
                if (shid != -1)
                {
                    dbc.executeNonQuery("DELETE FROM shifthastitlerelationship WHERE shid='" + shid + "'");
                    dbc.executeNonQuery("DELETE FROM datehasshiftrelationship WHERE shid='" + shid + "'");
                    dbc.executeNonQuery("DELETE FROM personhaspreferredshiftrelationship WHERE shid='" + shid + "'");
                    dbc.executeNonQuery("DELETE FROM personhasassaignedshiftrelationship WHERE shid='" + shid + "'");
                    dbc.executeNonQuery("DELETE FROM shiftinsidedepartmentrelationship WHERE shid='" + shid + "'");
                    dbc.executeNonQuery("DELETE FROM shiftneedsskillrelationship WHERE shid='" + shid + "'");
                    return true;

                }
                return false;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        /// <summary>
        /// returns whether or not this shift is being used anywhere. this is just a double check to make sure
        /// everything relating to this shift is removed to the shift itself can be taken out
        /// </summary>
        /// <returns></returns>
        private bool checkShiftUsed()
        {
            try
            {
                if (shid != -1)
                {
                    DataTable table = dbc.query("SELECT * FROM datehasshiftrelationship WHERE shid='" + shid + "'");
                    if ((table != null) && table.Rows.Count > 0) return true;

                    table = dbc.query("SELECT * FROM personhaspreferredshiftrelationship WHERE shid='" + shid + "'");
                    if ((table != null) && table.Rows.Count > 0) return true;

                    table = dbc.query("SELECT * FROM shifthastitlerelationship WHERE shid='" + shid + "'");
                    if ((table != null) && table.Rows.Count > 0) return true;

                    table = dbc.query("SELECT * FROM personhasassaignedshiftrelationship WHERE shid='" + shid + "'");
                    if ((table != null) && table.Rows.Count > 0) return true;

                    table = dbc.query("SELECT * FROM shiftinsidedepartmentrelationship WHERE shid='" + shid + "'");
                    if ((table != null) && table.Rows.Count > 0) return true;

                    table = dbc.query("SELECT * FROM shiftneedsskillrelationship WHERE shid='" + shid + "'");
                    if ((table != null) && table.Rows.Count > 0) return true;
                }

                return false;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        /// <summary>
        /// returns whether or not this shift is in the database
        /// </summary>
        /// <returns></returns>
        private bool checkShiftExists()
        {
            try
            {
                DataTable table = dbc.query("SELECT shid FROM Shift WHERE name='" + this.name + "' AND starttime='" + this.startTime + "' AND endtime='" + this.endTime + "'");
                if ((table != null) && (table.Rows.Count > 0))
                {
                    return true;
                }
                return false;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        private int findShiftID()
        {
            try
            {
                DataTable table = dbc.query("SELECT shid FROM Shift WHERE name='" + name + "' AND starttime = '" + startTime + "' AND endtime='" + endTime + "'");
                if ((table != null) && (table.Rows.Count > 0))
                {
                    return (int)table.Rows[0]["shid"];
                }
                else return -1;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        #endregion

        #region public

        public int writeOutObjectTree(int uniqueCounter, int sepCounter, int spaceIndex)
        {
            String spaces = "";
            Person oldPerson = null;
            int personCounter = 0;

            if (persons.Count > 0)
            {
                 oldPerson = persons[0];
            }

            for (int i = 0; i < spaceIndex; i++) { spaces = spaces + "\t"; }
            UTIL.sayDebug("(" + uniqueCounter + ")" + spaces + "(" + sepCounter + ")" + this.GetType().Name);

            foreach (Person person in persons)
            {
                if (!person.Equals(oldPerson))
                {
                    uniqueCounter++;
                    oldPerson = person;
                    personCounter++;
                }
               uniqueCounter =  person.writeOutObjectTree(uniqueCounter, personCounter, spaceIndex+1);
            }

            return uniqueCounter;
        }


        public void setWithIndex(Person person) {
            persons.Add(person);
        }

        /// <summary>
        /// returns all teh shifts within a certain timespan
        /// THIS IS INDEPENDANT FROM THE SCHEDULE's MASTERLISTS
        /// </summary>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <returns></returns>
        public static List<Shift> loadAllShiftsWithin(DateTime startTime, DateTime endTime)
        {
            try
            {
                List<Shift> shiftList = new List<Shift>();

                DatabaseConnection dbc = new DatabaseConnection();
                DataTable table = dbc.query("SELECT shid FROM Shift WHERE starttime>='" + startTime + "' AND endtime<='" + endTime + "'");
                if ((table != null) && (table.Rows.Count > 0))
                {
                    foreach (DataRow row in table.Rows)
                    {
                        Shift shift = new Shift();
                        shift.shid = (int)row["shid"];
                        shift.loadData();
                        shiftList.Add(shift);
                    }
                }

                return shiftList;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }


        /// <summary>
        /// loads every shift in one list and returns it. not recommended because it definitely doesnt scale well
        /// THIS IS INDEPENDANT FROM THE SCHEDULE'S MASTER LIST
        /// </summary>
        /// <returns></returns>
        public static List<Shift> loadAllShifts()
        {
            try
            {
                List<Shift> shiftList = new List<Shift>();

                DatabaseConnection dbc = new DatabaseConnection();
                DataTable table = dbc.query("SELECT shid FROM Shift");
                if ((table != null) && table.Rows.Count > 0)
                {
                    foreach (DataRow row in table.Rows)
                    {
                        Shift shift = new Shift();
                        shift.shid = (int)row["shid"];
                        shift.loadData();
                        shiftList.Add(shift);
                    }
                }

                return shiftList;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }


        /// <summary>
        /// clone constructor, used ONLY when cloning.
        /// </summary>
        /// <returns></returns>
        public Shift Clone()
        {
            return new Shift(this);
        }

        /// <summary>
        /// personalizes this shift for this person. removes all people not equal to this given person
        /// </summary>
        /// <param name="person">the person you want to filter</param>
        public void personalize(Person person)
        {
            for (int i = 0; i <= persons.Count - 1; i++)
            {
                if (persons.ElementAt(i).PERID == person.PERID)
                {
                    persons.RemoveAt(i);
                    i--;
                }
            }
        }

        /// <summary>
        /// removes all of this Shift data from the database
        /// WARNING : THIS CANNOT BE UNDONE
        /// </summary>
        public int removeData()
        {
            try
            {
                if (shid != -1)
                {
                    removeRelations();
                    if (!checkShiftUsed())
                    {
                        dbc.executeNonQuery("DELETE FROM Shift WHERE shid='" + shid + "'");
                        return 0;
                    }
                }
                return -1;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        /// <summary>
        /// saves all of this Shift instance's data into the database
        /// </summary>
        public int saveDataSimple()
        {
            try
            {
                if (shid == -1)
                {
                    if (!checkShiftExists())
                    {
                        dbc.executeNonQuery("INSERT INTO Shift (name, starttime, endtime) values ('" + this.name + "','" + this.startTime + "','" + this.endTime + "')");
                    }
                    this.shid = findShiftID();
                }
                else
                {
                    //save
                    dbc.executeNonQuery("UPDATE Shift SET name='" + name + "', starttime='" + startTime + "', endtime='" + endTime + "' WHERE shid='" + shid + "'");
                }

                return this.shid;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        /// <summary>
        /// saves all of this Shift instance's data into the database
        /// </summary>
        public int saveData()
        {
            try
            {
                if (shid == -1)
                {
                    if (!checkShiftExists())
                    {
                        dbc.executeNonQuery("INSERT INTO Shift (name, starttime, endtime) values ('" + this.name + "','" + this.startTime + "','" + this.endTime + "')");
                    }
                    this.shid = findShiftID();
                }
                else
                {
                    //save
                    dbc.executeNonQuery("UPDATE Shift SET name='" + name + "', starttime='" + startTime + "', endtime='" + endTime + "' WHERE shid='" + shid + "'");
                }

                //---------------------------------------
                if (titleNeeded != -1)
                {
                    //remove old relationship
                    dbc.executeNonQuery("DELETE FROM shifthastitlerelationship WHERE shid = '" + shid + "'");

                    //save new relationship
                    dbc.executeNonQuery("INSERT INTO shifthastitlerelationship (shid, tid) values ('" + this.shid + "','" + titleNeeded + "')");
                }

                
                //--------------------------------------



                //---------------------------------------
                if (dayOfWeek.getDOWID != -1)
                {
                    //remove old relationship
                    dbc.executeNonQuery("DELETE FROM shifthasdayofweekrelationship WHERE shid = '" + shid + "'");


                    int dowid = dayOfWeek.saveData();


                    //save new relationship
                    dbc.executeNonQuery("INSERT INTO shifthasdayofweekrelationship (shid, dowid) values ('" + this.shid + "','" + dowid + "')");
                }

                //--------------------------------------


                //-----------------------------------------

                //remove old relationship
                dbc.executeNonQuery("DELETE FROM personhasassaignedshiftrelationship WHERE shid='" + ShiftID + "'");

                foreach (Person p in persons)
                {

                    int perid = p.saveData();
                    if (perid != -1)
                    {
                        //save new relationship
                        dbc.executeNonQuery("INSERT INTO personhasassaignedshiftrelationship (perid, shid) VALUES ('" + perid + "','" + this.shid + "')");
                    }
                }
                //-----------------------------------------

                //-----------------------------------------
                //remove old relationships
                dbc.executeNonQuery("DELETE FROM shiftneedsskillrelationship WHERE shid='" + shid + "'");

                foreach (Skill skill in skills)
                {
                    int perid = skill.saveData();
                    if (skill.SkillID != -1)
                    {
                        //save new relationship
                        dbc.executeNonQuery("INSERT INTO shiftneedsskillrelationship (shid, sid) VALUES ('" + this.shid + "','" + skill.SkillID + "')");
                    }
                }
                //-----------------------------------------

                return this.shid;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        /// <summary>
        /// loads all the data necessary for this class from it's own table
        /// like the others, make sure  you set the ID and the ConnectionString before loading
        /// </summary>
        public void loadData()
        {
            try
            {
                DataTable table = dbc.query("SELECT * FROM Shift WHERE shid = '" + this.shid + "'");
                if ((table != null) && table.Rows.Count > 0)
                {
                    foreach (DataRow row in table.Rows)
                    {
                        this.name = (string)row["name"]; //load name
                        this.startTime = (DateTime)row["starttime"]; //load start time
                        this.endTime = (DateTime)row["endtime"]; //load end time
                    }
                }

                //------------------------------------------------------
                // TITLE NEEDED
                 table = dbc.query("SELECT * FROM shifthastitlerelationship WHERE shid='" + shid + "'");
                 if ((table != null) && table.Rows.Count > 0)
                 {
                     foreach (DataRow row in table.Rows)
                     {
                         this.titleNeeded = (int)row["tid"];
                     }
                 }
                //------------------------------------------------------


                table = dbc.query("SELECT perid FROM personhasassaignedshiftrelationship WHERE shid='" + shid + "'");
                if ((table != null) && table.Rows.Count > 0)
                {
                    foreach (DataRow row in table.Rows)
                    {
                        // hand person ID to schedule
                        // schedule loads person into the master list
                        // schedule returns that person pointer back to this
                        // add person pointer to local list
                        if (schedulePointer != null)
                        {
                            persons.Add(schedulePointer.loadPersonToMasterList((int)row["perid"]));
                        }
                        else
                        {
                            //old method (local only)
                            Person person = new Person();
                            person.PERID = (int)row["perid"];
                            person.loadData();
                            persons.Add(person);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }

            #region deprecated
            /*foreach (Department d in deprecated_departments)
            {
                d.loadData();
            }*/
            /*reader = dbc.query("SELECT deid FROM shiftinsidedepartmentrelationship S WHERE S.shid = '" + this.shid + "'");
            if ((table != null) && table.Rows.Count > 0)
            {
                this.departmentID = (int)row[];
            }*/
            #endregion

        }

        /// <summary>
        /// returns a LOCAL list of Persons that are assigned to this shift 
        /// (this is independant from department, so its basically anyone working in the hospital at the selected shift)
        /// (this is also independant from the Schedule's MasterList of Persons, which means its more loading. )
        /// </summary>
        /// <returns></returns>
        public List<Person> getAssignedPersons()
        {
            try
            {
                List<Person> persons = new List<Person>();

                DataTable table = dbc.query("SELECT perid FROM personhasassaignedshiftrelationship WHERE shid = '" + this.shid + "'");
                if ((table != null) && table.Rows.Count > 0)
                {
                    Person person = new Person();
                    person.PERID = (int)table.Rows[0][0];
                    person.loadData();
                    persons.Add(person);
                }

                return persons;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }
        }

        /// <summary>
        /// returns a list of Persons that are assigned to this shift
        /// </summary>
        /// <returns></returns>
        public List<Person> getPreferredPersons()
        {
            try
            {
                List<Person> persons = new List<Person>();

                DataTable table = dbc.query("SELECT perid FROM personhaspreferredshiftrelationship WHERE shid = '" + this.shid + "'");
                if ((table != null) && table.Rows.Count > 0)
                {
                    Person person = new Person();
                    person.PERID = (int)table.Rows[0][0];
                    person.loadData();
                    persons.Add(person);
                }

                return persons;
            }
            catch (Exception e)
            {
                UTIL.sayDebug(e);
                throw e;
            }

        }

        /// <summary>
        /// returns a list of all the persons related to this Shift, whether they prefer it or got assigned to it
        /// </summary>
        /// <returns></returns>
        public List<Person> getAllRelatedPersons()
        {
            List<Person> persons = getAllRelatedPersons();
            persons.AddRange(getPreferredPersons());

            //remove all redundant ones
            for (int i = 0; i <= persons.Count - 2; i++)
            {
                for (int q = i + 1; q <= persons.Count - 1; q++)
                {
                    if (persons.ElementAt(i).PERID == persons.ElementAt(q).PERID)
                    {
                        persons.RemoveAt(q);
                        q--;
                    }
                }
            }

            return persons;
        }

        /// <summary>
        /// returns the duration of this Shift as the original Timespan instance. 
        /// </summary>
        /// <returns></returns>
        public TimeSpan getDuration()
        {
            TimeSpan span = new TimeSpan();
            if (endTime < startTime) endTime.AddHours(24);
            span = endTime.Subtract(startTime);
            return span;
        }

        #endregion

        #region CLONE
        object ICloneable.Clone()
        {
            return new Shift(this);
        }
        #endregion
    }
}

